import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;

/**
 * Created with IntelliJ IDEA.
 * Description:服务器
 * User: wx
 * Date: 2024-03-14
 * Time: 13:20
 */

//服务器 需要先创建 DatapramSocket 对象
//       接下来要操作网卡都是通过 socket 对象来完成
//       socket 对象是在内存中的,针对内存对象进行操作就能影响到网卡
//        类似于遥控器/
public class UpdEchoserver {
    private DatagramSocket socket = null;

    public UpdEchoserver(int port) throws SocketException { //创建对象的时候,需要手动指定一个端口号
                                                            // (在运行一个服务器的时候,通常会手动指定端口号)
        socket = new DatagramSocket(port);//这里的端口号通过对象来进行传递的
    }

    //服务器的启动逻辑
    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
            //每次循环就是处理一个请求-响应过程
            // 1.获取请求并响应
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);//保存到消息正文
            socket.receive(requestPacket);
            //把读到的字节数组,转成 String方便后续逻辑处理
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

            // 2根据请求计算响应(对于回显服务器来说,这一步简单处理)
            String response = process(request);

            // 3.把响应返回客户端/
            //  构造一个 DatagramPacket 作为一个响应对象
            DatagramPacket responsePcket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePcket);

            System.out.printf("[%s: %d] req: %s, resp: %s\n",responsePcket.getAddress().toString(),
                    responsePcket.getPort(),request,response);
        }
    }
// 上述代码中,可以看到 UDP是无连接的通信, UDP socket 自身不保存对端的 ip 和 端口. 而是每个数据报中有一个
//      另外代码中,也没有"建立连接","接收连接" 操作
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UpdEchoserver server = new UpdEchoserver(9090); //这个端口号你可以指定任何想要的端口号,但是端口号有范围
        //                                                      大于1024,小于65535.
        //                                                      (前提是确保当前端口没有别别的端口占用)/
        server.start();
    }
}


//-----------------------------------------------------------
//我们这个程序一启动就需要 关联上/绑定上 一个操作系统中的端口号
//端口号 一个整数,用来区分一个主机上进行网络通信的程序
// 一个主机上的端口号只能被一个进程绑定
// 一个主机上的端口已经被 进程1 绑定了,进程2 也想绑定,就会失败
// 除非 进程1 释放了这个端口(比如,进程1 结束了)
// 反过来,一个进程可以绑定多个端口,很常见的
// 端口号 和 socket 对象是一一对应的
// 如果一个进程中创建了多个sock对象,自然就能绑定多个端口了
//------------------------------------------------------------


//--------------------------------------------------------------
// SocketExceptio 网络编程中常见的异常,通常表示 socket 创建失败.
// 比如 端口号已经被别的线程占用了,就会失败.
// -------------------------------------------------------------
// while 循环的作用
// 对于服务器来说,需要不断地收到请求,返回响应,再去收到请求,再返回响应
// 一个服务器单位时间能处理的请求,能返回的响应越多,那你的这个服务器就越厉害
// 服务器是 7 * 24 小时运行的.要持续不断地运行下去,这里的while true 也没有退出的必要
// 如果想要重启服务器.可以直接杀进程
//
// 此处是简单粗暴地
// 实际开发中商业级别的服务器,很可能需要实现"优雅退出"效果,确保当前正在处理请求都做完了再进行退出
//  服务器通常在Linux 系统上,Linux 结束一个进程使用 kill 命令
// --------------------------------------------------------------------------------/


//socket.receive(); 此处DatagramPacket 参数是输出型参数
// 外面文件IO 涉及到了
// 实际上DatagramPacket 就涉及到了这样的字节数组
//网络编程,读写 socket 本质上也是 文件IO/
// -----------------------------------------------------------------------

// DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
//                             通过字节数组保存到的消息正文(应用层数据包)
//                             也就是UDP数据报 载荷部分
//------------------------------------------------------------------------------



//----------------------------------------------------------------------------------------
//     DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);//保存到消息正文
//            socket.receive(requestPacket);
//   此处receive就从网卡能读取到一个UDP数据报,就被放到了 requestPacket 对象中.
//   其中 UDP 数据报的载荷部分就被放到 requestPacket 内置的字节数组中了
//   另外报头部分,也会被 requestPacket 的其他属性保存
//    出了 UPD报头之外. 比如 收到的数据 源IP 是啥
//    通过 requestPacket 还能知道数据从哪里来的(源IP 源端口)
//-----------------------------------------------------------------------------------


//----------------------------------------------------------------------------------------
//    DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);//保存到消息正文
//           socket.receive(requestPacket);
//    如果执行到 receive 的时候,此时还没有客户端发来请求, receive咋办?
//    先阻塞等待
//---------------------------------------------------------------------------------------/


//--------------------------------------------------------------------------------------------
//   requestPacket.getData() 获取字节数组内部持有的数组
//    String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
//    基于字节数组构成 String
//    字节数组里面保存的内容不一定就是二进制数据,也可能是文本数据
//    把文本数据交给 String 来保存
//    就算二进制数据, java 的 String 也是可以保存的.
//    getData()得到就是提供的字节数组,从 0号 位置开始构造 直到getLength()获取到的字节数组的有效长度 来构造String
//    requestPacket.getLength()
//    这个方法得到的长度指的是数组的有效长度,而不是最大长度4096
//    一定是要用 有效长度来构造这里的 String 使用最大长度,构造出来的后半部分都是空白
//-----------------------------------------------------------------------------------------


//------------------------------------------------------------------------------------------
//String response = process(request);
// 这个代码,要根据请求构造响应,通过 process 方法 来完成这个工作
// private String process(String request) {
//        return request;
//    }
//   此处只是一个 回显服务器,所以就只是一个单纯的 return
//   如果是具有特定业务的服务器,process 就可以写其他任何你想要的逻辑
//--------------------------------------------------------------------------------------------/

//socket.send();
// send 也需要一个 DatagramPacket 作为参数的
//  DatagramPacket responsePcket = new DatagramPacket(response.getBytes(StandardCharsets.UTF_8), response.getBytes().length,
//                    requestPacket.getSocketAddress());
//        构造响应的 Packet 对象里边就不是空白数组了,直接把 String 里包含的字节数组给拎过来了
// response.getBytes().length
// 为什么写成response.length() 不行?
//  response.getBytes().length(获取字长度) 单位是字节
// response.length() 单位是 字符
//  requestPacket.getSocketAddress());
//  构造这个对象的时候,还需要指定一个 socketAddress 进去
//  得到一个 INetAddress 这个指的是这个对象的 ip 和 端口号
//  和通信的对端(对于客户端)的 ip 和 端口
//  把客户端来的数据报中的 源ip 源端口,作为响应的 目的IP 目的端口号
//  此时就可以做到把消息返回给客户端这样的效果/